/******************************************************************************* * Copyright (c) 2015 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma.analysis; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtField; import javassist.bytecode.Descriptor; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Translator; public class TranslationIndex implements Serializable { private static final long serialVersionUID = 738687982126844179L; private Map<ClassEntry, ClassEntry> m_superclasses; private Multimap<ClassEntry, FieldEntry> m_fieldEntries; private Multimap<ClassEntry, BehaviorEntry> m_behaviorEntries; private Multimap<ClassEntry, ClassEntry> m_interfaces; public TranslationIndex() { m_superclasses = Maps.newHashMap(); m_fieldEntries = HashMultimap.create(); m_behaviorEntries = HashMultimap.create(); m_interfaces = HashMultimap.create(); } public TranslationIndex(TranslationIndex other, Translator translator) { // translate the superclasses m_superclasses = Maps.newHashMap(); for(Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_superclasses .entrySet()) m_superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); // translate the interfaces m_interfaces = HashMultimap.create(); for(Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_interfaces .entries()) m_interfaces.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); // translate the fields m_fieldEntries = HashMultimap.create(); for(Map.Entry<ClassEntry, FieldEntry> mapEntry : other.m_fieldEntries .entries()) m_fieldEntries.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); m_behaviorEntries = HashMultimap.create(); for(Map.Entry<ClassEntry, BehaviorEntry> mapEntry : other.m_behaviorEntries .entries()) m_behaviorEntries.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); } public void indexClass(CtClass c) { indexClass(c, true); } public void indexClass(CtClass c, boolean indexMembers) { ClassEntry classEntry = EntryFactory.getClassEntry(c); if(isJre(classEntry)) return; // add the superclass ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); if(superclassEntry != null) m_superclasses.put(classEntry, superclassEntry); // add the interfaces for(String interfaceClassName : c.getClassFile().getInterfaces()) { ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); if(!isJre(interfaceClassEntry)) m_interfaces.put(classEntry, interfaceClassEntry); } if(indexMembers) { // add fields for(CtField field : c.getDeclaredFields()) { FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); } // add behaviors for(CtBehavior behavior : c.getDeclaredBehaviors()) { BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); } } } public void renameClasses(Map<String, String> renames) { EntryRenamer.renameClassesInMap(renames, m_superclasses); EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries); EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries); } public ClassEntry getSuperclass(ClassEntry classEntry) { return m_superclasses.get(classEntry); } public List<ClassEntry> getAncestry(ClassEntry classEntry) { List<ClassEntry> ancestors = Lists.newArrayList(); while(classEntry != null) { classEntry = getSuperclass(classEntry); if(classEntry != null) ancestors.add(classEntry); } return ancestors; } public List<ClassEntry> getSubclass(ClassEntry classEntry) { // linear search is fast enough for now List<ClassEntry> subclasses = Lists.newArrayList(); for(Map.Entry<ClassEntry, ClassEntry> entry : m_superclasses.entrySet()) { ClassEntry subclass = entry.getKey(); ClassEntry superclass = entry.getValue(); if(classEntry.equals(superclass)) subclasses.add(subclass); } return subclasses; } public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) { for(ClassEntry subclassEntry : getSubclass(classEntry)) { out.add(subclassEntry); getSubclassesRecursively(out, subclassEntry); } } public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) { for(ClassEntry subclassEntry : getSubclass(classEntry)) { out.add(subclassEntry.getName()); getSubclassNamesRecursively(out, subclassEntry); } } public Collection<Map.Entry<ClassEntry, ClassEntry>> getClassInterfaces() { return m_interfaces.entries(); } public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) { return m_interfaces.get(classEntry); } public boolean isInterface(ClassEntry classEntry) { return m_interfaces.containsValue(classEntry); } public boolean entryExists(Entry entry) { if(entry instanceof FieldEntry) return fieldExists((FieldEntry)entry); else if(entry instanceof BehaviorEntry) return behaviorExists((BehaviorEntry)entry); else if(entry instanceof ArgumentEntry) return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry()); throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); } public boolean fieldExists(FieldEntry fieldEntry) { return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); } public boolean behaviorExists(BehaviorEntry behaviorEntry) { return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); } public ClassEntry resolveEntryClass(Entry entry) { if(entry instanceof ClassEntry) return (ClassEntry)entry; ClassEntry superclassEntry = resolveSuperclass(entry); if(superclassEntry != null) return superclassEntry; ClassEntry interfaceEntry = resolveInterface(entry); if(interfaceEntry != null) return interfaceEntry; return null; } public ClassEntry resolveSuperclass(Entry entry) { // this entry could refer to a method on a class where the method is not // actually implemented // travel up the inheritance tree to find the closest implementation while(!entryExists(entry)) { // is there a parent class? ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); if(superclassEntry == null) // this is probably a method from a class in a library // we can't trace the implementation up any higher unless we // index the library return null; // move up to the parent class entry = entry.cloneToNewClass(superclassEntry); } return entry.getClassEntry(); } public ClassEntry resolveInterface(Entry entry) { // the interfaces for any class is a forest // so let's look at all the trees for(ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) { ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); if(resolvedClassEntry != null) return resolvedClassEntry; } return null; } private boolean isJre(ClassEntry classEntry) { String packageName = classEntry.getPackageName(); return packageName != null && (packageName.startsWith("java") || packageName .startsWith("javax")); } public void write(OutputStream out) throws IOException { GZIPOutputStream gzipout = new GZIPOutputStream(out); ObjectOutputStream oout = new ObjectOutputStream(gzipout); oout.writeObject(m_superclasses); oout.writeObject(m_fieldEntries); oout.writeObject(m_behaviorEntries); gzipout.finish(); } @SuppressWarnings("unchecked") public void read(InputStream in) throws IOException { try { ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in)); m_superclasses = (HashMap<ClassEntry, ClassEntry>)oin.readObject(); m_fieldEntries = (HashMultimap<ClassEntry, FieldEntry>)oin.readObject(); m_behaviorEntries = (HashMultimap<ClassEntry, BehaviorEntry>)oin.readObject(); }catch(ClassNotFoundException ex) { throw new Error(ex); } } }